package im.composer.media.sound.render;

import im.composer.audio.engine.In;
import im.composer.audio.engine.Out;
import im.composer.audio.engine.Source;
import im.composer.generators.RedirectedAudioIn;
import jass.engine.BufferNotAvailableException;

import java.nio.FloatBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.jaudiolibs.audioservers.AudioClient;
import org.jaudiolibs.audioservers.AudioConfiguration;
import org.tritonus.share.sampled.AudioUtils;
import org.tritonus.share.sampled.FloatSampleBuffer;

public class SourcePlayer extends In implements AudioClient {

	private AudioConfiguration context;
	private transient FloatSampleBuffer buffer;
	private Map<int[], RedirectedAudioIn> channels_audioin = new HashMap<>();
	private transient long t;// time
	private float duration = Float.POSITIVE_INFINITY;
	protected transient double millisecond_per_frame;

	@Override
	public synchronized void configure(AudioConfiguration context) throws Exception {
		this.context = context;
		int channels = context.getOutputChannelCount();
		int bufz = context.getMaxBufferSize();
		buffer = new FloatSampleBuffer(channels,bufz,context.getSampleRate());
		millisecond_per_frame = AudioUtils.frames2MillisD(context.getMaxBufferSize(), context.getSampleRate());
		setTime(0);
		confgureRedirectedAudioIn();
	}

	private void confgureRedirectedAudioIn() {
		for(RedirectedAudioIn in:channels_audioin.values()){
			try {
				in.configure(context);
			} catch (Exception e) {
				continue;
			}
		}
	}

	public long getTime() {
		return t;
	}

	public void setTime(long t) {
		this.t = t;
		for (Source src : sourceContainer) {
			try {
				src.configure(context);
			} catch (Exception e) {
			}
			if (src instanceof Out) {//Especially InOut
				((Out)src).resetTime(t);
			}else{
				src.setTime(t);
			}
		}
	}

	public AudioConfiguration getContext() {
		return context;
	}

	public void setContext(AudioConfiguration context) {
		this.context = context;
		try {
			configure(context);
		} catch (Exception e) {
		}
	}

	public float getDuration() {
		return duration;
	}

	public void setDuration(float duration) {
		this.duration = duration;
	}

	public RedirectedAudioIn createAudioIn(int... channels) {
		if(channels.length<1){
			throw new IllegalArgumentException();
		}
		RedirectedAudioIn result = channels_audioin.get(channels);
		if (result == null) {
			result = new RedirectedAudioIn();
			result.setChannels(channels.length);
			if(context!=null){
				try {
					result.configure(context);
				} catch (Exception e) {
				}
			}
			channels_audioin.put(channels, result);
		}
		return result;
	}

	public void removeAudioIn(RedirectedAudioIn in) {
		int[] key = null;
		for (Entry<int[], RedirectedAudioIn> e : channels_audioin.entrySet()) {
			if (e.getValue().equals(in)) {
				key = e.getKey();
				break;
			}
		}
		if (key != null) {
			channels_audioin.remove(key);
		}
	}

	@Override
	public boolean process(long time, List<FloatBuffer> inputs, List<FloatBuffer> outputs, int nframes) {
		try {
			doInput(inputs);
		} catch (Exception e) {
		}
		try {
			doOutput(outputs);
		} catch (BufferNotAvailableException e) {
		}
		if(millisecond_per_frame * t > duration){
			return false;
		}
		return true;
	}

	private void doInput(List<FloatBuffer> inputs) throws Exception{
		for (Entry<int[], RedirectedAudioIn> e : channels_audioin.entrySet()) {
			int[] channels = e.getKey();
			RedirectedAudioIn in = e.getValue();
			FloatSampleBuffer buf = in.getBuffer();
			for (int i : channels) {
				if(i>inputs.size()||i>buf.getChannelCount()){
					continue;
				}
				float[] arr = inputs.get(i).array();
				buf.setRawChannel(i, arr);
			}
		}
	}

	private void doOutput(List<FloatBuffer> outputs) throws BufferNotAvailableException {
		getNextBuffer();
		int channels = Math.min(buffer.getChannelCount(), outputs.size());
		for (int i = 0; i < channels; i++) {
			float[] data = buffer.getChannel(i);
			FloatBuffer buf = outputs.get(i);
			buf.clear();
			buf.put(data);
		}

	}

	private synchronized void getNextBuffer() throws BufferNotAvailableException {
		buffer.makeSilence();
		for (Source src : sourceContainer) {
			FloatSampleBuffer buf = src.getBuffer(t);
			buffer.mix(buf);
		}
		t++;
	}

	@Override
	public void shutdown() {

	}

}
